Jx.declare('Jx.data.tree.Node', {

	$extend: 'Jx.Base',

	$requires: [

		'Jx.lib.Hash',

		'Jx.data.Collection'

	],

	$actions: [

//-- w3c
		'getAttribute',

		'setAttribute',

		'appendChild',

		'insertBefore',

		'removeChild',

//-- non-w3c
		'insertChildren',
		
		'appendChildren'

	],

	nextSibling: null,

	previousSibling: null,

	childNodes: null,

	firstChild: null,

	lastChild: null,

	parentNode: null,

	owner: null,

	nodeName: 'node',

	nodeValue: '',

	objectId: undefined,

	id: undefined,

	attributes: undefined,

	leaf: false,

	INSERT_OWN: 1,

	INSERT_MOVE: 2,

	INSERT_REORDER: 3,

	constructor: function(attributes){

		this.childNodes = Jx.instantiate('Jx.data.Collection');

		this.attributes = this.$cache;

		this.$super(arguments);

		this.onApplyAttributes(

			Jx.isObject(attributes) ?

				attributes

				:

				{}

		);

	},

	onDestroy: function(){

		//-- destroy childnodes first
		for( var node, children = this.childNodes, l = children.length; l--; ){
			
			//-- owner will remove it from parentNode
			children[l].destroy();
			
		}

		this.$super();

	},

	onApplyAttributes: function(attributes){

		Jx.each(attributes, function(n, v, o){

			this.setAttribute(n, v);

		});

	},

	onGetAttribute: function(operation, name){

		var value = this.get(name);

		operation.returnValue = value == null || typeof value == 'undefined' ?

			null

			:

			value;
	},

	onSetAttribute: function(operation, name, value){

		this.set(

			name,

			value == null || typeof value == 'undefined' ?

				null

				:

				value

		);

	},

	onAfterSetAttribute: function(operation, name, value){

		this.dispatch('change', name, value);

	},

	onInsertBefore: function(operation, node, before){

		var children = this.childNodes,

			old = node.parentNode,

			mode = operation.mode,

			previous, next;

		if (mode == this.INSERT_OWN){

			this.onInsertFromAnotherOwner(operation, node)

		} else {

			this.onInsertByRelocation(operation, node, mode == this.INSERT_REORDER);

		}

		node.parentNode = this;

		operation.returnValue = node;

	},

	onInsertFromAnotherOwner: function(operation, node){

		var index = operation.index;

		this.owner.registerNode( node );

		this.childNodes.add( node, node.id,

			index == -1 ?

				this.childNodes.length

				:

				index

		);

		this.reintegrateChild(node);

		operation.eventName = 'add';

	},

	onInsertByRelocation: function(operation, node, reorder){

		var index = operation.index,

			children = this.childNodes,

			len = children.length,

			old = node.parentNode;

		operation.eventName = 'move';

		operation.parentNodeBefore = old;

		if (index == -1){

			index = reorder ?

				len - 1

				:

				len;

		}

		//-- swap nodes
		if (reorder){

			var currentNode = children.itemAt(currentNode);

			children.swap( index, children.lastIndexOf(node) );

			this.reintegrateChild(node);

			this.reintegrateChild(currentNode);

		//-- insert
		} else {

			//-- silently remove from old parent node
			if (old){

				var oldChildren = old.childNodes,

					oldIndex = oldChildren.lastIndexOf(node);

				oldChildren.removeAt(oldIndex);

				old.reintegrateChild(

					oldChildren.itemAt(oldIndex)

				);

			}

			children.add( node, node.id, index );

			this.reintegrateChild(node);

		}

	},

	onAfterInsertBefore: function(operation, node){

		var name = operation.eventName;

		//-- newly created or newly transferred node
		if (name == 'add'){

			this.dispatch( name, node, operation.index );

		//-- moved or reordered
		} else {

			this.dispatch( name, node, operation.index, operation.parentNodeBefore );

		}

	},

	onAppendChild: function(operation, node){

		this.onInsertBefore(operation, node, null);

	},

	onAfterAppendChild: function(operation, node){

		this.onAfterInsertBefore(operation, node, null);

	},

	onRemoveChild: function(operation, node){

		var index = operation.index,

			reintegrateTarget = node.previousSibling || node.nextSibling;

		this.childNodes.removeAt(index);

		if (reintegrateTarget){

			this.reintegrateChild( reintegrateTarget );

		}

		delete node.parentNode;

		operation.returnValue = node;

	},

	onAfterRemoveChild: function(operation, node, destroy){

		this.dispatch('remove', node, operation.index);

		if (destroy === true){

			node.destroy();

		}

	},

	onInsertChildren: function(operation){

		var index = operation.index,

			rows = operation.data,

			modes = operation.modes,

			children = this.childNodes;

		for( var mode, node, c = 0, l = rows.length; c < l; c++ ){

			operation.index = index + c;

			mode = modes[c];

			node = rows[c];

			if (mode == this.INSERT_OWN){

				this.onInsertFromAnotherOwner(operation, node);

			} else {

				this.onInsertByRelocation(operation, node, mode == this.INSERT_REORDER);

			}

			this.onAfterInsertBefore(operation, node);

		}
		
		operation.returnValue = rows;

	},
	
	onAppendChildren: function(operation){
		
		this.onInsertChildren(operation);
		
	},

	allowGetAttribute: function(operation, name){

		operation.returnValue = null;

		return Jx.isString(name, true);

	},

	allowSetAttribute: function(operation, name, value){

		return Jx.isString(name, true);

	},

	allowInsertBefore: function(operation, node, before){

		operation.returnValue = null;

		var owner = this.getOwner(),

			allow = !!owner && owner.isValidNode(node) && node !== before && !this.leaf,

			children = this.childNodes,
			

			nodeOwner, nodeParent;

		//-- gather properties
		if (allow){

			nodeOwner = node.getOwner();

			nodeParent = node.parentNode;

			operation.index = Jx.isNumber(before) && before >= 0 && before < children.length ?

				before

				:


				children.lastIndexOf(before);

			operation.mode = nodeParent === this ?

				this.INSERT_REORDER

				:

				node.getOwner() === owner ?

					this.INSERT_MOVE

					:

					this.INSERT_OWN;

		}

		return allow;

	},

	allowAppendChild: function(operation, node){

		return this.allowInsertBefore(operation, node, null);

	},

	allowRemoveChild: function(operation, node, destroy){

		var index = operation.index = this.childNodes.lastIndexOf(node);

		return index != -1 && !this.isDestroyed();

	},

	allowInsertChildren: function(operation, data, before){
		
		operation.returnValue = [];

		var isArray = Jx.isArray(data),

			owner = this.owner,

			children = this.childNodes;

		if (Jx.isObject(data)){

			data = [ data ];

			isArray = true;

		} else if (isArray){

			data = data.slice(0);

		}

		//-- finalize data
		if (isArray && owner && !this.leaf){

			var requiredClass = owner.requireNodeClass,

				modes = operation.modes = [],

				index;

			for(var row, l = data.length; l--;){

				row = data[l];

				if (!owner.isValidNode(row) && Jx.isObject(row)){

					row = owner.createNode(row);

				}

				if ( owner.isValidNode(row) ){

					modes[l] = row.parentNode === this ?

						this.INSERT_REORDER

						:

						row.owner === owner ?

							this.INSERT_MOVE

							:

							this.INSERT_OWN;

					data[l] = row;

				} else {

					data.splice(l, 1);

					modes.splice(l, 1);

				}

			}

			//-- finalize before
			operation.index = Jx.instanceOf(before, requiredClass) ?

				children.lastIndexOf(before)

				:

				Jx.isNumber(before) && before >= 0 && before < children.length ?

					before

					:

					-1;

		}

		return isArray && (operation.data = data).length > 0;

	},
	
	allowAppendChildren: function(operation, children){
		
		if (!Jx.isArray( children )){
			
			children = Array.prototype.slice.call(arguments, 1);
			
		}
		
		return this.allowInsertChildren(operation, children, null);
		
	},

	getOwner: function(){

		var owner = this.owner;

		return Jx.instanceOf(owner, 'Jx.data.tree.Owner') ?

			owner

			:

			null;

	},

	hasChild: function(node){

		return this.childNodes.lastIndexOf(node) != -1;

	},

	reintegrateChild: function(node){

		var children = this.childNodes,

			index = children.lastIndexOf(node),

			len, previous, next;

		if (index != -1){

			len = children.length;

			previous = children.itemAt(index - 1);

			next = children.itemAt(index + 1);

			//-- integrate sibling relation
			if (previous){

				node.previousSibling = previous;

				previous.nextSibling = node;

			} else {

				delete node.previousSibling;

			}

			if (next){

				node.nextSibling = next;

				next.previousSibling = node;

			}

			//-- integrate child relation
			if (!index){

				this.firstChild = node;

			}

			if ((index + 1) == len){

				this.lastChild = node;

			}

		}

	}



});
